package maggames.core.base;

import java.util.HashMap;

import maggames.core.Piece;
import maggames.core.Player;
import maggames.core.interfaces.GameBoard;

import org.apache.log4j.Logger;

/**
 * The BaseGameBoard class represents a basic implementation of the GameBoard interface and fufills all of the basic
 * needs and uses.  The class can be used as-is or extended to better fit the needs of the game.  <i>Note: This version is not thread-safe</i>
 * 
 * @author  BenjaminPLee
 * @version 1.0
 *
 */
public class BaseGameBoard implements GameBoard {
	private static final Logger log = Logger.getLogger(BaseGameBoard.class);
	
	/**
	 * The game board's internal width.
	 */
	protected int width;
	
	/**
	 * The game board's internal height.
	 */
	protected int height;
	
	/**
	 * The internal representation of the board (width, height)
	 */
	protected Piece[][] board;
	
	/**
	 * Internal counts of varios piece types
	 */
	protected HashMap<String, Integer> pieceCounts;
	
	protected int totalPieces = 0;
	
	/**
	 * Sole constructor - Initializes game board with the given width and height.
	 * 
	 * @throws IllegalArgumentException If width or height is <= 0
	 * @param width board width - must be >= 0
	 * @param height board height - must be >= 0
	 */
	public BaseGameBoard(int width, int height) {
		log.debug("Creating BaseGame object with width: " + width + " and height: " + height + ".");
		
		if(width <= 0)
			throw new IllegalArgumentException("Invalid width attribute.  Must be greater than or equal to 1.");
		
		if(height <= 0)
			throw new IllegalArgumentException("Invalid height attribute.  Must be greater than or equal to 1.");
		
		this.width = width;
		this.height = height;
		
		board = new Piece[width][height];
		
		for(int x = 0; x < width; x++) {
			for(int y = 0; y < height; y++) {
				board[x][y] = Piece.NONE;
			}
		}
		
		pieceCounts = new HashMap<String, Integer>();
		
		Piece [] types = Piece.values();
		int numTypes = types.length;
		
		for(int i = 0; i < numTypes; i++) {
			pieceCounts.put(types[i].toString(), 0);
		}
		
		pieceCounts.put(Piece.NONE.toString(), width * height);
		totalPieces = 0;
	}
	
	/* (non-Javadoc)
	 * @see maggames.core.interfaces.GameBoard#getAsArray()
	 */
	public Piece[][] getAsArray() {
		log.debug("Returning game board as an array.");
		return board;
	}

	/* (non-Javadoc)
	 * @see maggames.core.interfaces.GameBoard#getHeight()
	 */
	public int getHeight() {
		return height;
	}

	/* (non-Javadoc)
	 * @see maggames.core.interfaces.GameBoard#getTile(int, int)
	 */
	public Piece getTile(int x, int y) {
		log.debug("Checking game board at loc (" + x + "," + y + ").");
		return board[x][y];
	}

	/* (non-Javadoc)
	 * @see maggames.core.interfaces.GameBoard#getWidth()
	 */
	public int getWidth() {
		return width;
	}

	/* (non-Javadoc)
	 * @see maggames.core.interfaces.GameBoard#setTile(int, int, maggames.core.Piece)
	 */
	public void setTile(int x, int y, Piece piece) {
		log.debug("Setting game board at loc (" + x + "," + y + ") to " + piece + ".");
		addAndRemovePiece(board[x][y], piece);
		
		if(board[x][y] == Piece.NONE && piece != Piece.NONE) {
			totalPieces++;
		}
		else if(board[x][y] != Piece.NONE && piece == Piece.NONE) {
			totalPieces--;
		}
		
		board[x][y] = piece;
	}
	
	/**
	 * Returns a Player object identifying which players have less (or equal) than the given threshold of pieces
	 * remaining on the board.  
	 * 
	 * @param threshold threshold amount to be considered (inclusive)
	 * @param includeSpecial should special player pieces be counted
	 * @param includeCommon should common pieces be counted
	 * @return The players who have fewer than the threshold of pieces on the board
	 */
	public Player playersUnderThreshold(int threshold, boolean includeSpecial, boolean includeCommon) {
		Player result = Player.NONE;
		
		int p1Count = 0;
		int p2Count = 0;
		
		p1Count += pieceCounts.get(Piece.P1.toString());
		p2Count += pieceCounts.get(Piece.P2.toString());
		
		if(includeSpecial) {
			p1Count += pieceCounts.get(Piece.P1SPECIAL.toString());
			p2Count += pieceCounts.get(Piece.P2SPECIAL.toString());
		}
		
		if(includeCommon) {
			p1Count += pieceCounts.get(Piece.COMMON.toString());
			p2Count += pieceCounts.get(Piece.COMMON.toString());
		}
		
		if(p1Count < threshold) {
			result = Player.add(result, Player.PLAYER1);
		}
		
		if(p2Count < threshold) {
			result = Player.add(result, Player.PLAYER2);
		}

		return result;
	}
	
	/**
	 * Returns the number of pieces matching the given piece types found on the current board.
	 * <blockquote>
	 * Example: numMatchingPieces(Piece.P1, Piece.P1_SPECIAL) --> returns the total number of Player 1 pieces on the board<br>
	 * Example: numMatchingPieces(Piece.COMMON) --> returns the count of only common pieces on the board<br>
	 * </blockquote>
	 * 
	 * @param pieces VarArg of those piece types that should be counted
	 * @return the number of pieces on the game board matching the above types
	 */
	public int numMatchingPieces(Piece ... pieces) {
		int result = 0;
		int numPieces = pieces.length;

		for(int i = 0; i < numPieces; i++) {
			result += pieceCounts.get(pieces[i].toString());
		}
		
		return result;
	}
	
	/**
	 * Increases the count of the given piece type by 1.
	 * 
	 * @param piece type of piece to be increased
	 */
	public void addPiece(Piece piece) {
		Integer count = pieceCounts.get(piece.toString());
		count++;
		pieceCounts.put(piece.toString(), count);
	}
	
	/**
	 * Reduces the count of the given piece type by 1.  If there are no pieces of
	 * the given type to begin with, the value is set to 0;
	 * 
	 * @param piece piece type to be removed from the totals
	 */
	public void removePiece(Piece piece) {
		Integer count = pieceCounts.get(piece.toString());
		count--;
		pieceCounts.put(piece.toString(), count);
	}
	
	/**
	 * Performs a removePiece action on the remove piece type, and an
	 * add action on the add piece type.
	 * 
	 * @param remove
	 * @param add
	 */
	public void addAndRemovePiece(Piece remove, Piece add) {
		removePiece(remove);
		addPiece(add);
	}

	/**
	 * @return the totalPieces
	 */
	public int getTotalPieces() {
		return totalPieces;
	}
}